package top.tenebrous.yzl.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import top.tenebrous.yzl.basic.model.CtsResult;
import top.tenebrous.yzl.entity.TestNo;
import top.tenebrous.yzl.entity.TestNoOffset;
import top.tenebrous.yzl.entity.TestOrgInfo;
import top.tenebrous.yzl.event.ArchiveResizeEvent;
import top.tenebrous.yzl.mapper.TestNoMapper;
import top.tenebrous.yzl.mapper.TestNoOffsetMapper;
import top.tenebrous.yzl.model.dto.TestNoDTO;
import top.tenebrous.yzl.service.IArchiveNoService;
import top.tenebrous.yzl.service.ITestOrgInfoService;
import top.tenebrous.yzl.utils.CommonUtils;
import top.tenebrous.yzl.utils.RedisLock;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service
@RefreshScope
public class ArchiveNoServiceImpl implements IArchiveNoService {

    @Autowired
    private ITestOrgInfoService testOrgInfoService;

    @Autowired
    private TestNoMapper testNoMapper;

    @Autowired
    private TestNoOffsetMapper testNoOffsetMapper;

    @Autowired
    private ApplicationContext applicationContext;

    @Resource
    private RedisTemplate<String, Long> redisTemplate;

    @Value("${default.num}")
    private Long DEFALUT_NUM = 100L;

    @Value("${default.capacity:10}")
    private Long DEFAULT_INITIAL_CAPACITY;

    public CtsResult<Integer> initBitMap() {
        // 初始化流程
        // 1、获取区域列表
        List<TestOrgInfo> orgInfos = testOrgInfoService.selectAreaCode();
        Set<String> areaCodes = orgInfos.stream().map(TestOrgInfo::getAreaCode).collect(Collectors.toSet());
        // 2、获取区域偏移量
        List<TestNoOffset> noOffsets = testNoOffsetMapper.selectList(new LambdaQueryWrapper<TestNoOffset>()
                .in(TestNoOffset::getAreaCode, areaCodes)
        );
        // 循环判断，写入redis
        for (String areaCode : areaCodes) {
            Map<String, TestNoOffset> offsetMap = noOffsets.stream().collect(Collectors.toMap(TestNoOffset::getAreaCode, Function.identity()));
            TestNoOffset offset = offsetMap.get(areaCode);
            writeToRedisAndDB(areaCode, offset);
        }
        return CtsResult.success();
    }

    /**
     * 将数据写入redis和数据库
     * 1、如果offset表数据不存在，则从0开始  自增数最大值为终止
     * 2、如果offset表数据存在，则从当前偏移量开始
     * 3、校验当前位图总数与已使用数 是否一致，如果一致，向后重新获取档案数据，避免每次都从头开始
     *
     *
     * @param areaCode              行政区域编码
     * @param offset                行政区域对应的偏移表数据
     */
    private TestNoOffset writeToRedisAndDB(String areaCode, TestNoOffset offset) {
        log.info("ArchiveNoServiceImpl.writeToRedisAndDB request===>areaCode:[{}] offset:{}", areaCode, JSON.toJSONString(offset));
        Long offsetNumStart = 0L;
        Long offsetNumEnd = 0L;
        if (Objects.nonNull(offset)) {
            // 当前偏移量
            offsetNumStart = offset.getStartIncrVal();
            offsetNumEnd = offset.getEndIncrVal();
        }

        // 生成redis-key: ima-public-health:340000000000:0:99
        String bitKey = CommonUtils.generationRedisKey(areaCode, offsetNumStart, offsetNumEnd);

        // 判断是否有bitMap, 不存在，则初始化
        if (!Boolean.TRUE.equals(redisTemplate.hasKey(bitKey))) {
            // 获取档案
            List<TestNoDTO> codes = testNoMapper.selectCodeByAreaCode(areaCode, DEFALUT_NUM, offsetNumStart);
            if (!CollectionUtils.isEmpty(codes)) {
                offsetNumEnd = codes.get(codes.size() - 1).getIncrNum();
                // 初始化的key 从0开始
                bitKey = CommonUtils.generationRedisKey(areaCode, offsetNumStart, offsetNumEnd);

                // 计算当前位图总数
                Long sumIncrVal = ((offsetNumEnd - offsetNumStart) + 1);
                log.info("位图:[{}]总容量:[{}]", bitKey, sumIncrVal);

                Triple<Boolean, Boolean, TestNoOffset> pair =
                    checkIncrementIsContinuity(sumIncrVal, codes, offset, areaCode);

                if (!pair.getLeft()) {
                    return writeToRedisAndDB(areaCode, pair.getRight());
                }

                if (pair.getMiddle() != null && pair.getMiddle()) {
                    TestNoOffset pairRight = pair.getRight();
                    bitKey = CommonUtils.generationRedisKey(areaCode, offsetNumStart, pairRight.getEndIncrVal());
                    for (TestNoDTO code : codes) {
                        if (code.getIncrNum() <= pairRight.getActivityId()) {
                            redisTemplate.opsForValue().setBit(bitKey, code.getIncrNum(), true);
                            log.info("REDIS KEY:[{}] SET BIT MAP SUCCESS, INDEX:[{}]", bitKey, JSON.toJSONString(code));
                        }
                    }
                } else {
                    // 设置进bitmap中
                    for (TestNoDTO testNo : codes) {
                        // 重置key
                        // 计算索引位
                        TestNoOffset pairRight = pair.getRight();
                        Long index = testNo.getIncrNum();
                        if (pairRight.getStartIncrVal() != null) {
                            index = transferCode(pairRight.getStartIncrVal(), testNo.getIncrNum());
                        }
                        redisTemplate.opsForValue().setBit(bitKey, index, true);
                        log.info("REDIS KEY:[{}] SET BIT MAP SUCCESS, INDEX:[{}]", bitKey, JSON.toJSONString(testNo));
                    }
                }

                // 回写数据库
                try {
                    if (Objects.nonNull(offset) && offset.getId() != null) {
                        offset.setAreaCode(areaCode);
                        offset.setStartIncrVal(offset.getStartIncrVal() == null ? 0L : offset.getStartIncrVal());
                        offset.setEndIncrVal(offsetNumEnd);
                        offset.setActivityId((codes.get(codes.size() - 1).getId()));
                        offset.setUpdateDate(new Date());
                        offset.setCount(sumIncrVal);
                        testNoOffsetMapper.updateById(offset);
                    } else {
                        TestNoOffset offset1 = new TestNoOffset();
                        offset1.setAreaCode(areaCode);
                        offset1.setStartIncrVal(Objects.isNull(offset) ? 0L : codes.get(0).getIncrNum());
                        offset1.setCount(sumIncrVal);
                        if (pair.getMiddle() != null && pair.getMiddle()) {
                            TestNoOffset pairRight = pair.getRight();
                            offset1.setEndIncrVal(pairRight.getEndIncrVal());
                            offset1.setCount(((pairRight.getEndIncrVal() - (Objects.isNull(offset) ? 0L : codes.get(0).getIncrNum()) + 1)));
                        } else {
                            offset1.setEndIncrVal(codes.get(codes.size() - 1).getIncrNum());
                        }
                        offset1.setActivityId((codes.get(codes.size() - 1).getId()));

                        offset1.setUpdateDate(new Date());
                        offset1.setCreateDate(new Date());
                        testNoOffsetMapper.insert(offset1);
                    }
                } catch (Exception e) {
                    log.error("初始化:[{}]档案编号，保存偏移量数据异常，回滚redis数据:{}", areaCode, e);
                    redisTemplate.delete(bitKey);
                    log.info("回滚redis数据完成 回滚key:{}", bitKey);
                }
            }
        }
        return offset;
    }

    /**
     *  校验获取的一批档案数据,是否连续
     *                           5000  -  0
     *  即: 未使用编号 <= 10  || (最大值 - 起始值) > 100
     *
     *  校验俩种情况:
     *  1: 获取的编号列表中,可用数据不足10个,需要向后扩大100,保证位图中有超过10条未使用数据
     *      已知总数
     *      已知数据总数
     *  2: 获取的编号列表中,可用数据超出上限
     * @param sumIncrVal
     * @param codes
     * @param areaCode
     */
    private Triple<Boolean,Boolean,TestNoOffset> checkIncrementIsContinuity(Long sumIncrVal, List<TestNoDTO> codes, TestNoOffset offset, String areaCode) {

        if (Objects.isNull(offset)) {
            offset = new TestNoOffset();
        }
        // 获取的编号列表中,可用数据不足10个
        Long offsetNumEnd = codes.get(codes.size() - 1).getIncrNum();
        /*
           START = 421
           END = 530
           SUM = (530 - 421) + 1 = 110   ===> 查询出来的连续数据应为110条
           查询总数为: 100条
           ====>  说明110个连续字段中有100条是存在的,而不存在的为10条,所以未使用的为10条
         */
        // 总数(位图总数 >= 100) - 减去集合总数(查询出来的数据  <= 100 )
        //  312 - 211 = 104(说明,当前位图最多有104个自增数据)
        // 如果位图总数 == DEFALUT_NUM(100) 说明当前位图,正好全部为已使用编号,此时再加入位图,毫无意义,需要重新获取
        if (Objects.equals(sumIncrVal, DEFALUT_NUM)) {
            // 查询档案信息
            List<TestNoDTO> newCodes = testNoMapper.selectCodeByAreaCode(areaCode, DEFALUT_NUM, offsetNumEnd + 1);
            // 更新offset,重复操作
            offset.setStartIncrVal(offsetNumEnd + 1);
            offset.setEndIncrVal(newCodes.get(newCodes.size() - 1).getIncrNum());
            return new ImmutableTriple<>(Boolean.FALSE,null, offset);
        }

        // 如果集合前相邻数据差距过大,超过2000,需要重新计算
        return checkCodes(codes, offset);
    }

    /**
     * 二分法快速获取集合中相差在500左右的前后俩个数字
     *
     * 500 600 800 2300 5000 5270 6100 9000 15000 19000
     *
     * 如果:  1、5000 - 500 > 2000
     *          是:
     *
     * @param codes
     * @return
     */
    private Triple<Boolean, Boolean, TestNoOffset> checkCodes(List<TestNoDTO> codes, TestNoOffset offset) {
        final Long first = codes.get(0).getIncrNum();
        final Long sum = first + 2000;
        Long last = codes.get(codes.size() - 1).getIncrNum();
        if (last - first <= 2000L) {
            return new ImmutableTriple<>(Boolean.TRUE,Boolean.FALSE,  offset);
        }
        for (int i = 1; i < codes.size(); i++) {
            last = codes.get(i).getIncrNum();       // 集合的每个元素
            if (last - first > 2000) {
                // 如果该数值 - 起始值 > 2000  判断尾数 是否大于起始值 + 2000
                if (last > sum) {
                    offset.setActivityId(codes.get(i - 1).getIncrNum());
                    last = sum;
                }
            }
        }
        offset.setStartIncrVal(first);
        offset.setEndIncrVal(last);

        return new ImmutableTriple<>(Boolean.TRUE,Boolean.TRUE,  offset);
    }

    @Override
    public CtsResult<String> getArchiveNo(String areaCode) {

        // 获取分布式锁
        RedisLock redisLock = new RedisLock(redisTemplate, CommonUtils.generationRedisKey(areaCode, "GET_CODE_LOCK"));

        try {
            if (!redisLock.tryLock(50000)) {
                // 兜底方案 走原获取逻辑
                return finalSolution(areaCode);
            } else {
                // 获取偏移量
                TestNoOffset offset = testNoOffsetMapper.selectOne(new LambdaQueryWrapper<TestNoOffset>().eq(TestNoOffset::getAreaCode, areaCode));
                // 如果偏移量为空，则初始化偏移量
                if (Objects.isNull(offset)) {
                    offset = writeToRedisAndDB(areaCode, null);
                }

                 /*
                 校验当前位图的剩余可用数量
                 因为位图统计，只能统计为1的数量，所以，无法准确的获取真数量
                 所以需要在偏移量表中记录其实自增与结束自增的位置，方便统计，总共自增量
                 */
                Long endIncrVal = offset.getEndIncrVal();
                Long startIncrVal = offset.getStartIncrVal();
                Long sumIncrVal = ((endIncrVal - startIncrVal) + 1);            // 总容量
                        // 总    容量
                // 生成redisKey
                String bitKey = CommonUtils.generationRedisKey(areaCode, startIncrVal, endIncrVal);

                // 统计当前位图，已使用数量
                Long count = redisTemplate.execute((RedisCallback<Long>) cbk -> cbk.bitCount(bitKey.getBytes()));
                Long notUsed = (sumIncrVal - count);
                log.info("位图总容量:[{}] 已使用容量:[{}] 未使用容量:[{}]", sumIncrVal, count, notUsed);

                // 如果未使用数量小于等于10个，需要扩容
                if (Objects.equals(DEFAULT_INITIAL_CAPACITY, notUsed)) {
                    // TODO 异步通知生成新的位图
                    log.info("异步通知生成新的位图: 未使用{}  阈值:{}", notUsed, DEFAULT_INITIAL_CAPACITY);
                    applicationContext.publishEvent(new ArchiveResizeEvent(this, areaCode, offset));
                }

                // 如果未使用数量等于0 从新的位图获取数据
                if (notUsed == 0) {
//                    // 先去异步通知生成位图,如果没有生成的话
//                    applicationContext.publishEvent(new ArchiveResizeEvent(this, areaCode, offset));
                    // 重新获取偏移数据

                    TestNoOffset newOffset;
                    String newBitKey;
                    long timeout = 3000L;
                    long start = System.currentTimeMillis();
                    while (true) {
                        if (System.currentTimeMillis() - start > timeout) {
                            log.info("获取新位图超时,请稍后重试");
                            throw new RuntimeException("获取新位图超时,请稍后重试");
                        }
                        String key = CommonUtils.generationRedisKey(areaCode, "RESIZE_LOCK");       // 扩容分布式锁的key
                        if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
                            break;
                        }
                        log.info("当前新位图redis生成中,请等待");
                    }
                    newOffset = testNoOffsetMapper.selectOne(new LambdaQueryWrapper<TestNoOffset>().eq(TestNoOffset::getAreaCode, areaCode));
                    log.info("获取偏移量:{}", JSON.toJSONString(newOffset, true));
                    newBitKey = CommonUtils.generationRedisKey(areaCode, newOffset.getNextStartIncrVal(), newOffset.getNextEndIncrVal());
                    log.info("当前Redis key:[{}] 的位图编号已获取完毕，开始从新的位图中获取:[{}]", bitKey, newBitKey);
                    // 获取新key数据
                    String finalNewBitKey = newBitKey;
                    Long index = redisTemplate.execute((RedisCallback<Long>) cbk -> cbk.bitPos(finalNewBitKey.getBytes(), false));
                    final Long newIndex = index;
                    Long data = transferIndex(index, newOffset);
                    log.info("key:[{}] bitMap中最先生成为0的索引:{} 编号:[{}]",finalNewBitKey, newIndex, data);

                    // 更新偏移量
                    newOffset.setLastActivityId(newOffset.getActivityId());
                    newOffset.setActivityId(newOffset.getNextActivityId());
                    newOffset.setNextActivityId(null);
                    newOffset.setStartIncrVal(newOffset.getNextStartIncrVal() == null ? 0 : newOffset.getNextStartIncrVal());
                    newOffset.setEndIncrVal(newOffset.getNextEndIncrVal() == null ? 0 : newOffset.getNextEndIncrVal());
                    newOffset.setNextStartIncrVal(null);
                    newOffset.setNextEndIncrVal(null);
                    newOffset.setUpdateDate(new Date());
                    newOffset.setCount((newOffset.getEndIncrVal() - newOffset.getStartIncrVal()) + 1);
                    testNoOffsetMapper.updateById(newOffset);

                    // 延时删除key
                    redisTemplate.expire(bitKey, 1, TimeUnit.HOURS);

                    // 数据入库
                    AtomicInteger bits = new AtomicInteger(5);
                    String code = areaCode + String.format("%0" + bits + "d", data);
                    // 校验标号
                    if (!checkArchiveNo(code, newBitKey, newIndex, index)) {
                        return finalSolution(areaCode);
                    }
                    return CtsResult.success(code);
                }

                // 未使用数量 大于10，正常获取
                // 获取编号
                Long index = redisTemplate.execute((RedisCallback<Long>) cbk -> cbk.bitPos(bitKey.getBytes(), false));
                final Long newIndex = index;
                Long data = transferIndex(index, offset);
                log.info("key:[{}] bitMap中最先生成为0的索引:{} 编号:[{}]",bitKey, newIndex, data);
                // 数据入库
                AtomicInteger bits = new AtomicInteger(5);
                String code = areaCode + String.format("%0" + bits + "d", data);
                // 校验标号
                if (!checkArchiveNo(code, bitKey, newIndex, index)) {
                    return finalSolution(areaCode);
                }
                return CtsResult.success(code);
            }
        } catch (Exception e) {
            log.error("获取编号异常:{}", e);

        } finally {
            redisLock.unlock();
        }

        return finalSolution(areaCode);
    }

    /**
     * 生成编号
     *
     * @return
     */
    @Override
    public CtsResult<Void> generationCode() {
        ThreadPoolTaskExecutor localBootAsyncExecutor = (ThreadPoolTaskExecutor) applicationContext.getBean("localBootAsyncExecutor");
        // 获取区域
        List<TestOrgInfo> orgInfos = testOrgInfoService.selectAreaCode();

        Map<String, List<TestOrgInfo>> listMap = orgInfos.stream().collect(Collectors.groupingBy(TestOrgInfo::getAreaCode));

        Random random = new Random();
        listMap.forEach((areaCode, v) -> {

            for (int j = 1; j < 10; j++) {
                int finalJ = j;
                localBootAsyncExecutor.submit(new Runnable() {
                    @Override
                    public void run() {
                        int num = finalJ;
                        for (int i = (num - 1) * 10000; i < 100000 * num; i++) {
                            AtomicInteger bits = new AtomicInteger(5);
                            int randomInt = random.nextInt(11);
                            String code = areaCode + String.format("%0" + bits + "d", i);
                            if (randomInt % 3 == 0) {       // 随机数余5==0
                                TestNo testNo = new TestNo();
                                testNo.setTestArchriveNo(code);
                                testNo.setCreateDate(new Date());
                                testNo.setUpdateDate(new Date());
                                testNoMapper.insert(testNo);
                                log.info("新增编号完成:{}", code);
                            }
                        }
                    }
                });
            }

        });
        return CtsResult.success();
    }

    /**
     * 二分法获取
     *
     * @param areaCode
     * @return
     */
    @Override
    public CtsResult<String> dichotomy(String areaCode) {
        // 获取档案信息
        List<TestNoDTO> testNoDTOS = testNoMapper.selectCodeByAreaCode(areaCode, DEFALUT_NUM, 0L);
        return null;
    }

    @Override
    public CtsResult<String> init(String areaCode) {
        long start = System.currentTimeMillis();
        Long offset = 0L;
        while (true) {
            List<TestNoDTO> testNoDTOS = testNoMapper.selectCodeByAreaCode(areaCode, 5000L, offset);
            if (CollectionUtils.isEmpty(testNoDTOS)) {
                break;
            }

            String key= "IMA-PUBLIC-HEALTH:" + areaCode;
            // 设置到bitmap
            testNoDTOS.forEach(testNoDTO -> {
                // 设置到bitmap
                redisTemplate.opsForValue().setBit(key, testNoDTO.getIncrNum(), true);
            });
            offset = testNoDTOS.get(testNoDTOS.size() - 1).getIncrNum() + 1;
            log.info("处理[{}]条数据完成，耗时:{}",testNoDTOS.size(), System.currentTimeMillis() - start);
        }
        log.info("初始化完成，总耗时:{}", System.currentTimeMillis() - start);
        return CtsResult.success("设置完成");
    }

    @Override
    public CtsResult<String> getNewArchiveNo(String areaCode) {
        String key= "IMA-PUBLIC-HEALTH:" + areaCode;
        Long index = redisTemplate.execute((RedisCallback<Long>) cbk -> cbk.bitPos(key.getBytes(), false));
        if (index == -1) {
            return CtsResult.success("没有可用编号");
        }
        AtomicInteger bits = new AtomicInteger(5);
        String code = areaCode + String.format("%0" + bits + "d", index);

        // 获取数量
        Long count = redisTemplate.execute((RedisCallback<Long>) cbk -> cbk.bitCount(key.getBytes()));
        log.info("key:[{}] 已使用数量:[{}]", key, count);

        TestNo no = testNoMapper.selectOne(new LambdaQueryWrapper<TestNo>().eq(TestNo::getTestArchriveNo, code));
        if (Objects.isNull(no)) {
            TestNo testNo1 = new TestNo();
            testNo1.setTestArchriveNo(code);
            testNo1.setCreateDate(new Date());
            testNo1.setUpdateDate(new Date());
            testNoMapper.insert(testNo1);
            log.info("生成编号:{}", code);
            // TODO 获取不代表使用，此时无需设置索引位
            redisTemplate.opsForValue().setBit(key, index, true);
            return CtsResult.success(code);
        } else {
            return CtsResult.error(500,"编号已存在");
        }
    }

    /**
     * 校验编号是否存在
     * 正常情况下，无需设置索引位，等真正的表单提交后，再设置
     *
     * @param code
     * @param newBitKey
     * @param newIndex
     * @param index
     * @return
     */
    private boolean checkArchiveNo(String code, String newBitKey, Long newIndex, Long index) {
        boolean flag = false;
        TestNo no = testNoMapper.selectOne(new LambdaQueryWrapper<TestNo>().eq(TestNo::getTestArchriveNo, code));
        if (Objects.isNull(no)) {
            TestNo testNo1 = new TestNo();
            testNo1.setTestArchriveNo(code);
            testNo1.setCreateDate(new Date());
            testNo1.setUpdateDate(new Date());
            testNoMapper.insert(testNo1);
            log.info("生成编号:{}", code);
            // TODO 获取不代表使用，此时无需设置索引位
            redisTemplate.opsForValue().setBit(newBitKey, newIndex, true);
            return flag = true;

        }
        log.info("key:[{}] bitMap索引:{} 对应的编号已存在，存在编号:{}", newBitKey, index, JSON.toJSONString(no));
        return flag;

    }

    /**
     * 转换索引位编号
     *
     * @param index
     * @param newOffset
     * @return
     */
    private Long transferIndex(Long index, TestNoOffset newOffset) {
        Long startIncrVal = newOffset.getStartIncrVal();
        return startIncrVal + index;
    }

    /**
     * start : 300
     * end: 399
     * @param start
     * @param end
     * @return
     */
    private Long transferCode(Long start, Long end) {
        return end - start;
    }

    /**
     * 查询该行政区划的最大值 + 1的编号
     * 该编号，不设置如redis
     *
     * @param areaCode
     * @return
     */
    private CtsResult<String> finalSolution(String areaCode) {
        String code = testNoMapper.finalSolution(areaCode);
        AtomicInteger bits = new AtomicInteger(5);
        String archiveNo = areaCode + String.format("%0" + bits + "d", (Long.parseLong(code) + 1));
        return CtsResult.success(archiveNo);
    }

}
